import { Menu, Tray, app, clipboard, ipcMain, nativeImage, shell } from "electron";
import log from "electron-log";
import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import os from "os";
import { utils } from "../base/utils.js";

/**
 * @file Creates App instance
 * @author CiderCollective
 */

/** @namespace */
export class AppEvents {
  private protocols: string[] = ["ame", "cider", "itms", "itmss", "musics", "music"];
  private plugin: any = undefined;
  private tray: any = undefined;
  private i18n: any = undefined;

  /** @constructor */
  constructor() {
    this.start();
  }

  /**
   * Handles all actions that occur for the app on start (Mainly commandline arguments)
   * @returns {void}
   */
  private start(): void {
    AppEvents.initLogging();
    console.info("[AppEvents] App started");

    /**********************************************************************************************************************
     * Startup arguments handling
     **********************************************************************************************************************/
    if (app.commandLine.hasSwitch("version") || app.commandLine.hasSwitch("v")) {
      console.log(app.getVersion());
      app.exit();
    }

    // Verbose Check
    if (app.commandLine.hasSwitch("verbose")) {
      console.log("[Cider] User has launched the application with --verbose");
    }

    // Log File Location
    if (app.commandLine.hasSwitch("log") || app.commandLine.hasSwitch("l")) {
      console.log(join(app.getPath("userData"), "logs"));
      app.exit();
    }

    // Try limiting JS memory to 350MB.
    app.commandLine.appendSwitch("js-flags", "--max-old-space-size=350");

    // Expose GC
    app.commandLine.appendSwitch("js-flags", "--expose_gc");

    if (process.platform === "win32") {
      app.setAppUserModelId(app.getName()); // For notification name
    }

    /***********************************************************************************************************************
     * Commandline arguments
     **********************************************************************************************************************/
    switch (utils.getStoreValue("visual.hw_acceleration") as string) {
      default:
      case "default":
        app.commandLine.appendSwitch("enable-accelerated-mjpeg-decode");
        app.commandLine.appendSwitch("enable-accelerated-video");
        app.commandLine.appendSwitch("disable-gpu-driver-bug-workarounds");
        app.commandLine.appendSwitch("ignore-gpu-blacklist");
        app.commandLine.appendSwitch("enable-native-gpu-memory-buffers");
        app.commandLine.appendSwitch("enable-accelerated-video-decode");
        app.commandLine.appendSwitch("enable-gpu-rasterization");
        app.commandLine.appendSwitch("enable-native-gpu-memory-buffers");
        app.commandLine.appendSwitch("enable-oop-rasterization");
        break;

      case "webgpu":
        console.info("[AppEvents] WebGPU is enabled.");
        app.commandLine.appendSwitch("enable-unsafe-webgpu");
        if (process.platform === "linux") {
          app.commandLine.appendSwitch("enable-features", "Vulkan");
        }
        break;

      case "disabled":
        console.info("[AppEvents] Hardware acceleration is disabled.");
        app.commandLine.appendSwitch("disable-gpu");
        app.disableHardwareAcceleration();
        break;
    }

    if (process.platform === "linux") {
      app.commandLine.appendSwitch("disable-features", "MediaSessionService");

      if (os.version().includes("SteamOS")) {
        app.commandLine.appendSwitch("enable-features", "UseOzonePlatform");
        app.commandLine.appendSwitch("ozone-platform", "x11");
      }
    }

    /***********************************************************************************************************************
     * Protocols
     **********************************************************************************************************************/
    /**  */
    if (process.defaultApp) {
      if (process.argv.length >= 2) {
        this.protocols.forEach((protocol: string) => {
          app.setAsDefaultProtocolClient(protocol, process.execPath, [resolve(process.argv[1])]);
        });
      }
    } else {
      this.protocols.forEach((protocol: string) => {
        app.setAsDefaultProtocolClient(protocol);
      });
    }
  }

  public quit() {
    console.log("[AppEvents] App quit");
  }

  public ready(plug: any) {
    this.plugin = plug;
    console.log("[AppEvents] App ready");

    AppEvents.setLoginSettings();
  }

  public bwCreated() {
    app.on("open-url", (event, url) => {
      event.preventDefault();
      if (this.protocols.some((protocol: string) => url.includes(protocol))) {
        this.LinkHandler(url);
        console.log(url);
      }
    });

    if (process.platform === "darwin") {
      app.setUserActivity(
        "8R23J2835D.com.ciderapp.webremote.play",
        {
          title: "Web Remote",
          description: "Connect to your Web Remote",
        },
        "https://webremote.cider.sh",
      );
    }

    this.InstanceHandler();
    if (process.platform !== "darwin") {
      this.InitTray();
    }
  }

  /***********************************************************************************************************************
   * Private methods
   **********************************************************************************************************************/

  /**
   * Handles links (URI) and protocols for the application
   * @param arg
   */
  private LinkHandler(arg: string) {
    if (!arg) return;

    // LastFM Auth URL
    if (arg.includes("auth")) {
      const authURI = arg.split("/auth/")[1];
      if (authURI.startsWith("lastfm")) {
        // If we wanted more auth options
        console.log("token: ", authURI.split("lastfm?token=")[1]);
        utils
          .getWindow()
          .webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', ${JSON.stringify(authURI.split("lastfm?token=")[1])})`)
          .catch(console.error);
      }
    } else if (arg.includes("playpause")) {
      //language=JS
      utils.getWindow().webContents.executeJavaScript("MusicKitInterop.playPause()");
    } else if (arg.includes("nextitem")) {
      //language=JS
      utils.getWindow().webContents.executeJavaScript("app.mk.skipToNextItem()");
    }
    // Play
    else if (arg.includes("/play/")) {
      //Steer away from protocol:// specific conditionals
      const playParam = arg.split("/play/")[1];

      const mediaType = {
        "s/": "song",
        "a/": "album",
        "p/": "playlist",
      };

      for (const [key, value] of Object.entries(mediaType)) {
        if (playParam.includes(key)) {
          const id = playParam.split(key)[1];
          utils.getWindow().webContents.send("play", value, id);
          console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`);
        }
      }
    } else if (arg.includes("music.apple.com")) {
      // URL (used with itms/itmss/music/musics uris)
      console.log(arg);
      let url = arg.split("//")[1];
      console.warn(`[LinkHandler] Attempting to load url: ${url}`);
      utils.getWindow().webContents.send("play", "url", url);
    } else if (arg.includes("/debug/appdata")) {
      shell.openPath(app.getPath("userData"));
    } else if (arg.includes("/debug/logs")) {
      shell.openPath(app.getPath("logs"));
    } else if (arg.includes("/discord")) {
      shell.openExternal("https://discord.gg/applemusic");
    } else if (arg.includes("/github")) {
      shell.openExternal("https://github.com/ciderapp/cider");
    } else if (arg.includes("/donate")) {
      shell.openExternal("https://opencollective.com/ciderapp");
    } else if (arg.includes("/beep")) {
      shell.beep();
    } else {
      utils.getWindow().webContents.executeJavaScript(`app.appRoute(${JSON.stringify(arg.split("//")[1])})`);
    }
  }

  /**
   * Handles the creation of a new instance of the app
   */
  private InstanceHandler() {
    // Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
    const gotTheLock = app.requestSingleInstanceLock();

    if (!gotTheLock) {
      // Runs on the new instance if another instance has been found
      console.log("[Cider] Another instance has been found, quitting.");
      app.quit();
    } else {
      // Runs on the first instance if no other instance has been found
      app.on("second-instance", (_event, startArgs) => {
        console.log("[InstanceHandler] (second-instance) Instance started with " + startArgs.toString());

        startArgs.forEach((arg) => {
          console.log(arg);
          if (arg.includes("cider://") || arg.includes("itms://") || arg.includes("itmss://") || arg.includes("music://") || arg.includes("musics://")) {
            console.debug("[InstanceHandler] (second-instance) Link detected with " + arg);
            this.LinkHandler(arg);
          } else if (arg.includes("--force-quit")) {
            console.warn("[InstanceHandler] (second-instance) Force Quit found. Quitting App.");
            app.quit();
          } else if (utils.getWindow()) {
            if (utils.getWindow().isMinimized()) utils.getWindow().restore();
            utils.getWindow().show();
            utils.getWindow().focus();
          }
        });
      });
    }
  }

  /**
   * Initializes the applications tray
   */
  private InitTray() {
    const icons = {
      win32: nativeImage.createFromPath(join(dirname(fileURLToPath(import.meta.url)), `../../resources/icons/icon.ico`)).resize({
        width: 32,
        height: 32,
      }),
      linux: nativeImage.createFromPath(join(dirname(fileURLToPath(import.meta.url)), `../../resources/icons/icon.png`)).resize({
        width: 32,
        height: 32,
      }),
      darwin: nativeImage.createFromPath(join(dirname(fileURLToPath(import.meta.url)), `../../resources/icons/icon.png`)).resize({
        width: 20,
        height: 20,
      }),
    };
    this.tray = new Tray(process.platform === "win32" ? icons.win32 : process.platform === "darwin" ? icons.darwin : icons.linux);
    this.tray.setToolTip(app.getName());
    this.setTray(false);

    this.tray.on("double-click", () => {  // supports windows and mac only
      if (utils.getWindow()) {
        if (utils.getWindow().isVisible()) {
          utils.getWindow().focus();
        } else {
          utils.getWindow().show();
        }
      }
    });

    this.tray.on("click", () => {
      if (utils.getWindow() && process.platform === "linux") {  // use single click to open when double doesn't work
        if (utils.getWindow().isVisible()) {
          utils.getWindow().focus();
        } else {
          utils.getWindow().show();
        }
      }
    });

    utils.getWindow().on("show", () => {
      this.setTray(true);
    });

    utils.getWindow().on("restore", () => {
      this.setTray(true);
    });

    utils.getWindow().on("hide", () => {
      this.setTray(false);
    });

    utils.getWindow().on("minimize", () => {
      this.setTray(false);
    });
  }

  /**
   * Sets the tray context menu to a given state
   * @param visible - BrowserWindow Visibility
   */
  private setTray(visible: boolean = utils.getWindow().isVisible()) {
    this.i18n = utils.getLocale(utils.getStoreValue("general.language"));

    const ciderIcon = nativeImage.createFromPath(join(dirname(fileURLToPath(import.meta.url)), `../../resources/icons/icon.png`)).resize({
      width: 24,
      height: 24,
    });

    const menu = Menu.buildFromTemplate([
      {
        label: app.getName(),
        enabled: false,
        icon: ciderIcon,
      },

      { type: "separator" },

      /* For now only idea i dont know if posible to implement

            this could be implemented in a plugin if you would like track info, it would be impractical to put listeners in this file. -Core
            {
                label: this.i18n['action.tray.listento'],
                enabled: false,
            },

            {
                visible: visible,
                label: 'track info',
                enabled: false,
            },

            {type: 'separator'},
            */

      {
        visible: !visible,
        label: this.i18n["term.playpause"],
        click: () => {
          utils.getWindow().webContents.executeJavaScript("MusicKitInterop.playPause()");
        },
      },

      {
        visible: !visible,
        label: this.i18n["term.next"],
        click: () => {
          utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`);
        },
      },

      {
        visible: !visible,
        label: this.i18n["term.previous"],
        click: () => {
          utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`);
        },
      },

      { type: "separator", visible: !visible },

      {
        label: visible ? this.i18n["action.tray.minimize"] : `${this.i18n["action.tray.show"]}`,
        click: () => {
          if (utils.getWindow()) {
            if (visible) {
              utils.getWindow().hide();
            } else {
              utils.getWindow().show();
            }
          }
        },
      },
      {
        label: this.i18n["term.quit"],
        click: () => {
          app.quit();
        },
      },
    ]);
    this.tray.setContextMenu(menu);
  }

  /**
   * Initializes logging in the application
   * @private
   */
  private static initLogging() {
    log.transports.console.format = "[{h}:{i}:{s}.{ms}] [{level}] {text}";
    Object.assign(console, log.functions);
    console.debug = function (...args: any[]) {
      if (!app.isPackaged) {
        log.debug(...args);
      }
    };

    ipcMain.on("fetch-log", (_event) => {
      const data = readFileSync(log.transports.file.getFile().path, {
        encoding: "utf8",
        flag: "r",
      });
      clipboard.writeText(data);
    });
  }

  /**
   * Set login settings
   * @private
   */
  private static setLoginSettings() {
    if (utils.getStoreValue("general.onStartup.enabled")) {
      app.setLoginItemSettings({
        openAtLogin: true,
        path: app.getPath("exe"),
        args: [`${utils.getStoreValue("general.onStartup.hidden") ? "--hidden" : ""}`],
      });
    } else {
      app.setLoginItemSettings({
        openAtLogin: false,
        path: app.getPath("exe"),
      });
    }
  }
}
